using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using KaroEngine;
using KaroGame.FrontEnd.Menu;
using KaroEngine.Exceptions;

namespace KaroGame.FrontEnd
{
    #region MouseEvent
    public enum MouseEvent
    {
        Hover,
        LeftClick
    }
    #endregion

    #region GameType
    public enum GameType
    {
        PlrVsPlr,
        PlrVsCpu,
        CpuVsCpu
    }
    #endregion

    public class KaroGame : Microsoft.Xna.Framework.Game
    {
        #region Properties
        private const float BOARD_WIDTH = 5;
        private const float BOARD_HEIGHT = 4;
        private const float TILE_SPACING_MULTIPLIER = 1.0f;
        private const float KEYBOARD_ROTATION_SPEED = 1.5f;
        private const float KEYBOARD_ZOOM_SPEED = 0.01f;
        private const float KEYBOARD_MOVEMENT_SPEED = 0.075f;
        private const float MOUSE_MOVEMENT_MULTIPLIER = 50f;
        private const float MOUSE_ZOOM_MULTIPLIER = 0.0002f;
        private const float MOUSE_ROTATION_MULTIPLIER = 500f;
        public const float DEFAULT_NEAR_ZOOM_BOUND = 0.6f;
        public const float DEFAULT_FAR_ZOOM_BOUND = -0.75f;
        public const float DEFAULT_CENTRE_SCREEN_X = 0;
        public const float DEFAULT_CENTRE_SCREEN_Y = 0;
        private float nearZoomBound = 0.6f;
        private float farZoomBound = -0.75f;
        private Vector3 centreScreen = Vector3.Zero;

        private GraphicsDeviceManager graphicsDeviceManager;
        private SpriteBatch spriteBatch;
        private Texture2D backgroundTexture;

        private bool isKeyEscapeDown;
        private bool isKeyDDown;
        private bool isKeyRDown;
        private bool isKeyTDown;

        private bool isLeftMouseDown;
        private bool isMiddleMouseDown;
        private bool isRightMouseDown;
        private int scrollValue;
        private Ray mouseMoveRay;
        private Ray mouseLeftDownRay;
        private Ray mouseRightDownRay;

        private bool isAnimating;
        private bool isFirstCall;
        private bool isToTopAnimating;
        private bool isToPlayerAnimating;
        private bool isMovingConeAnimating;
        private bool isFlippingConeAnimating;
        private bool isToNewZoomBoundsAnimating;
        private bool isToCentreScreenAnimating;
        private bool isRotatingOneEightyDegrees;
        private bool isGamePaused;
        private bool isInitial = true;

        private int tileFrom;
        private int tileTo;
        private int tileToMove;
        private bool hasAddACone;
        private bool needToSelectCornerTile;
        private bool moveTile;
        private WaitForTurnAnimation waitForTurnAnimation;
        private bool humanMadeMove;
        private TimeSpan undoTimer = TimeSpan.Zero;
        private bool aiMadeMove;
        private int aiTileFrom;
        private int aiTileTo;
        private int aiTileToMove;

        private BasicEffect basicEffect;
        private Vector3 cameraPosition;
        private Vector3 cameraTarget;
        private Matrix rotationX = Matrix.Identity;
        private Matrix rotationZ = Matrix.Identity;
        private Matrix scaling = Matrix.Identity;
        private Matrix translation = Matrix.Identity;
        private float degreesOfRotationX;
        private float degreesOfRotationZ;
        private float amountOfScaling;
        private Vector3 amountOfTranslation = Vector3.Zero;

        private DebugController debugController;
        private ExceptionHandler exceptionHandler;
        private HUD hud;
        private TerminalState terminalState;
        private Designations designations;
        private TileList tileList;
        private ConeList coneList;

        private KaroMenu menu;
        public bool IsInMenu { get; set; }
        public bool IsGameStarted { get; set; }
        public GameType currentGameType;

        public bool IsBusy
        {
            get
            {
                if (this.waitForTurnAnimation.IsActive || isAnimating)
                {
                    return true;
                }
                return false;
            }
        }

        private bool isTerminal;

        public BasicEffect BasicEffect
        {
            get { return basicEffect; }
        }

        public SpriteBatch SpriteBatch
        {
            get { return spriteBatch; }
        }

        public bool IsAnimating
        {
            get { return isAnimating; }
        }

        public bool IsMovingConeAnimating
        {
            set { isMovingConeAnimating = value; }
        }

        public bool IsFlippingConeAnimating
        {
            set { isFlippingConeAnimating = value; }
        }
        
        public Matrix GetWorldMatrix
        {
            get { return basicEffect.World * rotationZ * rotationX * scaling * translation; }
        }

        public float BoardWidth
        {
            get { return BOARD_WIDTH; }
        }

        public float BoardHeight
        {
            get { return BOARD_HEIGHT; }
        }

        public float NearZoomBound
        {
            get { return nearZoomBound; }
            set
            {
                isToNewZoomBoundsAnimating = true;
                nearZoomBound = value;

                if (nearZoomBound < -0.8f)
                {
                    nearZoomBound = -0.8f;
                }
            }
        }

        public float FarZoomBound
        {
            get { return farZoomBound; }
            set
            {
                isToNewZoomBoundsAnimating = true;
                farZoomBound = value;

                if (farZoomBound < -0.9f)
                {
                    farZoomBound = -0.9f;
                }
            }
        }

        public Vector3 CentreScreen
        {
            get { return centreScreen; }
            set
            {
                isToCentreScreenAnimating = true;
                centreScreen = value;
            }
        }

        public TileList GetTiles
        {
            get { return tileList; }
        }
        public bool IsTerminal { get { return isTerminal; } }
        #endregion

        #region Constructors
        public KaroGame()
        {
            graphicsDeviceManager = new GraphicsDeviceManager(this);
            graphicsDeviceManager.PreparingDeviceSettings += new EventHandler<PreparingDeviceSettingsEventArgs>(graphicsDeviceManager_PreparingDeviceSettings);
            graphicsDeviceManager.PreferMultiSampling = true;
            graphicsDeviceManager.ApplyChanges();

            Content.RootDirectory = "Content";

            IsMouseVisible = true;
            Window.Title = "Karo";

            IsInMenu = true;
        }

        protected void graphicsDeviceManager_PreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
        {
            // This variable will hold the maximum possible multi sample quality (+ 1) for the current graphics device
            int multiSampleLevel = 0;

            // Check if we can handle eight samples, and get the maximum possible multi sample quality for the eight samples
            bool canHandleEightSamples = e.GraphicsDeviceInformation.Adapter.CheckDeviceMultiSampleType(DeviceType.Hardware,
                                                                            SurfaceFormat.Bgr32,
                                                                            false,
                                                                            MultiSampleType.EightSamples,
                                                                            out multiSampleLevel);

            // If we can handle eight samples, set the device to use it
            if (canHandleEightSamples)
            {
                e.GraphicsDeviceInformation.PresentationParameters.MultiSampleQuality = multiSampleLevel - 1;
                e.GraphicsDeviceInformation.PresentationParameters.MultiSampleType = MultiSampleType.EightSamples;
                return;
            }

            // Check if we can handle four samples, and get the maximum possible multi sample quality for the four samples
            bool canHandleFourSamples = e.GraphicsDeviceInformation.Adapter.CheckDeviceMultiSampleType(DeviceType.Hardware,
                                                                            SurfaceFormat.Bgr32,
                                                                            false,
                                                                            MultiSampleType.FourSamples,
                                                                            out multiSampleLevel);

            // If we can handle four samples, set the device to use it
            if (canHandleFourSamples)
            {
                e.GraphicsDeviceInformation.PresentationParameters.MultiSampleQuality = multiSampleLevel - 1;
                e.GraphicsDeviceInformation.PresentationParameters.MultiSampleType = MultiSampleType.FourSamples;
                return;
            }

            // Check if we can handle two samples, and get the maximum possible multi sample quality for the two samples
            bool canHandleTwoSamples = e.GraphicsDeviceInformation.Adapter.CheckDeviceMultiSampleType(DeviceType.Hardware,
                                                                            SurfaceFormat.Bgr32,
                                                                            false,
                                                                            MultiSampleType.TwoSamples,
                                                                            out multiSampleLevel);

            // If we can handle two samples, set the device to use it
            if (canHandleTwoSamples)
            {
                e.GraphicsDeviceInformation.PresentationParameters.MultiSampleQuality = multiSampleLevel - 1;
                e.GraphicsDeviceInformation.PresentationParameters.MultiSampleType = MultiSampleType.TwoSamples;
                return;
            }
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // Make a basic effect that will be applied to everything. This gives a camera viewpoint and a global rotation to everything.
            cameraPosition = new Vector3(0, 0, 10);
            RotateX( -45 );

            cameraTarget = Vector3.Zero;
            basicEffect = new BasicEffect(GraphicsDevice, null);
            basicEffect.World = Matrix.Identity;
            basicEffect.View = Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up);
            basicEffect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45),
                                                                          (float)SettingsManager.Instance.ResolutionWidth / 
                                                                          (float)SettingsManager.Instance.ResolutionHeight,
                                                                          0.1f,
                                                                          100f);
            SetResolution();

            //Add the HUD here
            hud = new HUD( this );
            hud.Initialize();

            // Add the services for the tiles and cones
            tileList = new TileList(this);
            coneList = new ConeList(this);

            menu = new KaroMenu(this);
            menu.Initialize();

            debugController = new DebugController(this);
            debugController.Initialize();

            exceptionHandler = new ExceptionHandler(this);
            Components.Add(exceptionHandler);

            terminalState = new TerminalState(this);
            Components.Add( terminalState );

            designations = new Designations(this);
            designations.Initialize();

            Services.AddService(typeof(ITileProvider), tileList);
            Services.AddService(typeof(IConeProvider), coneList);

            scrollValue = Mouse.GetState().ScrollWheelValue;

            waitForTurnAnimation = new WaitForTurnAnimation(this);
            Components.Add(waitForTurnAnimation);

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            backgroundTexture = Content.Load<Texture2D>("Background1");
            debugController.LoadContent();
            hud.LoadContent();
            designations.LoadContent();

            tileList.LoadContent();
            coneList.LoadContent();
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
        }
        #endregion

        #region Private Methods
        #region Selecting
        private void SelectObjectAt(Ray mouseRay, MouseEvent mouseEvent)
        {
            float distanceToCone = float.NaN;
            float distanceToTile = float.NaN;

            int selectableConeId = coneList.Select( mouseRay, ref distanceToCone );
            int selectableTileId = tileList.Select( mouseRay, ref distanceToTile );

            if ( ( distanceToTile.Equals( float.NaN ) && !distanceToCone.Equals( float.NaN ) ) ||
                 ( distanceToCone <= distanceToTile ) )
            {
                switch ( mouseEvent )
                {
                    case MouseEvent.Hover:
                        coneList.Deactivate();
                        tileList.Deactivate();
                        coneList.Activate( selectableConeId );
                        break;

                    case MouseEvent.LeftClick:
                        //if (!hasAddACone)
                        //{
                        if ( ( ( Engine.currentBoard.OppositePlayer == Player.White ) &&
                               coneList.Cones.Find( x => x.TileId == selectableConeId ).ConeType == ConeType.White ) ||
                             ( Engine.currentBoard.OppositePlayer == Player.Red &&
                               coneList.Cones.Find( x => x.TileId == selectableConeId ).ConeType == ConeType.Red ) )
                        {
                            coneList.Deselect();
                            coneList.Select( selectableConeId );
                            tileFrom = selectableConeId;
                        }
                        // }
                        break;
                }
            }
            else if ( ( distanceToCone.Equals( float.NaN ) && !distanceToTile.Equals( float.NaN ) ) ||
                      ( distanceToCone > distanceToTile ) )
            {
                switch ( mouseEvent )
                {
                    case MouseEvent.Hover:
                        coneList.Deactivate();
                        tileList.Deactivate();
                        tileList.Activate( selectableTileId );
                        break;

                    case MouseEvent.LeftClick:
                        //if (!hasAddACone)
                        //{
                        if ( needToSelectCornerTile )
                        {
                            coneList.Deselect();
                            coneList.Select( selectableTileId );

                            needToSelectCornerTile = false;
                            tileToMove = selectableTileId;
                        }
                        else
                        {
                            if ( coneList.Cones.Exists( x => x.TileId == selectableTileId ) )
                            {
                                if ( ( ( Engine.currentBoard.OppositePlayer == Player.White ) &&
                                       coneList.Cones.Find( x => x.TileId == selectableTileId ).ConeType == ConeType.White ) ||
                                     ( Engine.currentBoard.OppositePlayer == Player.Red &&
                                       coneList.Cones.Find( x => x.TileId == selectableTileId ).ConeType == ConeType.Red ) )
                                {
                                    coneList.Deselect();
                                    coneList.Select( selectableTileId );
                                    tileFrom = selectableTileId;
                                }
                            }
                            else
                            {
                                tileList.Deselect();
                                tileList.Select( selectableTileId );
                                if ( coneList.Cones.Count == 12 )
                                {
                                    tileTo = selectableTileId;
                                    needToSelectCornerTile = tileList[tileTo].Ghost;
                                }
                            }
                        }
                        //}
                        break;
                }
            }

            else
            {
                coneList.Deactivate();
                tileList.Deactivate();
            }
        }

        private Ray GetRealMouseCoordinates()
        {
            MouseState mouseState = Mouse.GetState();

            // Get the near and far plane mouse positions
            Vector3 mousePositionNear = new Vector3(mouseState.X, mouseState.Y, 0);
            Vector3 mousePositionFar = new Vector3(mouseState.X, mouseState.Y, 1);

            // Unproject the mouse positions to adjust for the 3D projection and the camera viewpoint
            Vector3 unprojectedMousePositionNear = GraphicsDevice.Viewport.Unproject(mousePositionNear,
                                                                                      basicEffect.Projection,
                                                                                      basicEffect.View,
                                                                                      Matrix.Identity);
            Vector3 unprojectedMousePositionFar = GraphicsDevice.Viewport.Unproject(mousePositionFar,
                                                                                     basicEffect.Projection,
                                                                                     basicEffect.View,
                                                                                     Matrix.Identity);

            // Determine the direction of the mouse ray
            Vector3 rayDirection = unprojectedMousePositionFar - unprojectedMousePositionNear;
            rayDirection.Normalize();

            // Create the mouse ray and return it
            return new Ray(unprojectedMousePositionNear, rayDirection);
        }

        private void ResetSelectedTiles()
        {
            if (coneList != null)
            {
                coneList.Deselect();
                coneList.Deactivate();
            }

            if (tileList != null)
            {
                tileList.Deselect();
                tileList.Deactivate();
            }

            tileFrom = tileTo = tileToMove = -1;
        }
        #endregion

        #region View
        private void RotateX(float degrees)
        {
            // Rotate the world over the X axis (up or down)
            if (degreesOfRotationX + degrees <= 0 && degreesOfRotationX + degrees > -90)
            {
                degreesOfRotationX += degrees;

                rotationX = Matrix.CreateRotationX(MathHelper.ToRadians(degreesOfRotationX));
            }
        }

        private void RotateZ(float degrees)
        {
            // Rotate the world over the Z axis (horizontally sideways)
            degreesOfRotationZ += degrees;

            if (degreesOfRotationZ < 0)
            {
                degreesOfRotationZ += 360;
            }
            else if (degreesOfRotationZ > 360)
            {
                degreesOfRotationZ -= 360;
            }

            rotationZ = Matrix.CreateRotationZ(MathHelper.ToRadians(degreesOfRotationZ));
        }

        private void Zoom(float amount)
        {
            // Zoom the view
            if (amountOfScaling + amount > farZoomBound && amountOfScaling + amount < nearZoomBound)
            {
                amountOfScaling += amount;

                scaling = Matrix.CreateScale(1 + amountOfScaling);
            }
        }

        private void Move(float amountX, float amountY)
        {
            amountOfTranslation.X += amountX;
            amountOfTranslation.Y += amountY;

            translation = Matrix.CreateTranslation(amountOfTranslation);
        }
        #endregion

        #region Animations
        private void StepToTopAnimation()
        {
            // Every step rotates and zooms the board so that we will 
            // view te board from the top after the animation

            // Determine the direction of rotation, depending on the rotation we already have, to get it
            // to 0 degrees
            float rotateXDirection = degreesOfRotationX == 0 ? 0 : degreesOfRotationX < 0 ? 1 : -1;
            if (Math.Abs(degreesOfRotationX) < KEYBOARD_ROTATION_SPEED && rotateXDirection != 0)
            {
                // If the rotation around the X axis is smaller than the step size, set the rotation to 0
                rotationX = Matrix.Identity;
                degreesOfRotationX = 0;
            }
            else if (rotateXDirection != 0)
            {
                // If we can rotate (direction != 0), do it
                RotateX(KEYBOARD_ROTATION_SPEED * rotateXDirection);
            }

            // Determine the direction of rotation, depending on the rotation we already have, to get it
            // to 0 degrees
            float rotateZDirection = degreesOfRotationZ % 180 == 0
                                             ? 0
                                             : (degreesOfRotationZ > 90 && degreesOfRotationZ < 180) || degreesOfRotationZ > 270
                                                       ? 1
                                                       : -1;
            if (Math.Abs(degreesOfRotationZ % 180) < KEYBOARD_ROTATION_SPEED && rotateZDirection != 0)
            {
                // If the rotation around the Z axis is smaller than the step size, set the rotation to 0
                rotationZ = Matrix.Identity;
                degreesOfRotationZ = 0;
            }
            else if (rotateZDirection != 0)
            {
                // If we can rotate (direction != 0), do it
                RotateZ(KEYBOARD_ROTATION_SPEED * rotateZDirection);
            }

            // Determine the direction of scaling, depending on the scaling we already have, to get it
            // to 0 degrees
            float fourtyFourPercentScaling = (( nearZoomBound + Math.Abs( farZoomBound ) ) * (1f / 9f * 5f)) + farZoomBound;
            fourtyFourPercentScaling -= fourtyFourPercentScaling % KEYBOARD_ZOOM_SPEED;
            float scaleDirection = amountOfScaling == fourtyFourPercentScaling ? 0 : amountOfScaling < fourtyFourPercentScaling ? 1 : -1;
            if (Math.Abs( fourtyFourPercentScaling - amountOfScaling) < KEYBOARD_ZOOM_SPEED && scaleDirection != 0)
            {
                // If the scaling is smaller than the step size, set the scaling to 0
                scaling = Matrix.CreateScale(1 + fourtyFourPercentScaling);
                amountOfScaling = fourtyFourPercentScaling;
            }
            else if (scaleDirection != 0)
            {
                // If there is a direction to scale in, do it
                Zoom(KEYBOARD_ZOOM_SPEED * scaleDirection);
            }

            // Determine the direction to move in, depending on the offset we already have, to center the board on the
            // screen again
            Vector2 translateDirection = new Vector2(amountOfTranslation.X == centreScreen.X ? 0 : amountOfTranslation.X < centreScreen.X ? 1 : -1,
                                                      amountOfTranslation.Y == centreScreen.Y ? 0 : amountOfTranslation.Y < centreScreen.Y ? 1 : -1);
            if (translateDirection != Vector2.Zero)
            {
                // If we can move, determine the actual translation. This is a correction on the movement being smaller than
                // the step size. If it is smaller, just move that bit and not a whole step
                Vector2 actualTranslation =
                        new Vector2(
                                Math.Abs(centreScreen.X - amountOfTranslation.X) < KEYBOARD_MOVEMENT_SPEED
                                        ? Math.Abs(amountOfTranslation.X - centreScreen.X) * translateDirection.X
                                        : KEYBOARD_MOVEMENT_SPEED * translateDirection.X,
                                Math.Abs(centreScreen.Y - amountOfTranslation.Y) < KEYBOARD_MOVEMENT_SPEED
                                        ? Math.Abs(amountOfTranslation.Y - centreScreen.Y) * translateDirection.Y
                                        : KEYBOARD_MOVEMENT_SPEED * translateDirection.Y);
                Move(actualTranslation.X, actualTranslation.Y);
            }

            // If we can no longer rotate, scale and move, we have reached the top view and can stop animating
            if (degreesOfRotationX == 0 && degreesOfRotationZ % 180 == 0 && amountOfScaling == fourtyFourPercentScaling && amountOfTranslation == centreScreen)
            {
                isToTopAnimating = false;
            }
        }

        private void StepToPlayerAnimation()
        {
            // Every step rotates and zooms the board so that we will view 
            // the board from the player's point of view after the animation

            // Check if we should rotate. We always rotate on the first time, or when the degrees of rotation
            // around the Z axis is not 0 or 180 or 360
            bool shouldRotate = isFirstCall || (degreesOfRotationZ != 0 && degreesOfRotationZ != 180 && degreesOfRotationZ != 360);

            // If this is the first call to the method, set a variable detemining wether or not we are rotating
            // from 1 side to the other, or correcting the view to the nearest wide side of the board
            if (isFirstCall)
            {
                isRotatingOneEightyDegrees = degreesOfRotationZ % 180 == 0;
            }

            // If we should rotate, do it, if not, cancel the animation and set some variables back to their defaults
            if (shouldRotate)
            {
                bool isFullStep = true;

                // Determine the direction we have to rotate to. If we are rotation from 1 side to the other,
                // we always go in the -1 direction
                float rotateZDirection = 0;
                if (isRotatingOneEightyDegrees)
                {
                    rotateZDirection = degreesOfRotationZ == 0 ? 1 : degreesOfRotationZ < 0 ? -1 : 1;

                    isFullStep = degreesOfRotationZ % 180 > KEYBOARD_ROTATION_SPEED;
                }
                // If we are correcting the view to the nearest wide side of the board, our direction is determined by
                // the rotation the board already has. This simply goes to the nearest wide side of the board
                else
                {
                    rotateZDirection = degreesOfRotationZ % 180 == 0
                                 ? 0
                                 : (degreesOfRotationZ > 90 && degreesOfRotationZ < 180) || degreesOfRotationZ > 270
                                           ? 1
                                           : -1;

                    isFullStep = (degreesOfRotationZ % 180 > KEYBOARD_ROTATION_SPEED);
                }

                // Rotate with the keyboard speed in the determined direction
                if (isFullStep || isFirstCall)
                {
                    RotateZ(KEYBOARD_ROTATION_SPEED * rotateZDirection);
                }
                else
                {
                    RotateZ((Math.Abs(degreesOfRotationZ) % 180) * rotateZDirection);
                }
            }
            else
            {
                isToPlayerAnimating = false;
                isRotatingOneEightyDegrees = false;
            }

            isFirstCall = false;
        }

        private void StepToNewZoomBoundsAnimation()
        {
            // If the amountOfScaling is within the zoom bounds, the direction is 0 (and we should stop the animation). If it is not,
            // the direction is determined by the zoombound it is crossing. 
            float scaleDirection = amountOfScaling < nearZoomBound && amountOfScaling > farZoomBound
                                           ? 0
                                           : amountOfScaling > nearZoomBound ? -1 : amountOfScaling < farZoomBound ? 1 : 0;
            
            // If the scale direction is not 0, we are trying to zoom in a direction
            if (scaleDirection != 0)
            {
                // If our amount of scaling is almost the near zoom bound, set it to the near zoom bound and set the proper scaling.
                // Also set the direction to 0, because we don't need to zoom anymore as we're now in the zoom bounds again.
                if ( Math.Abs( nearZoomBound - amountOfScaling ) < KEYBOARD_ZOOM_SPEED )
                {
                    amountOfScaling = nearZoomBound;
                    scaling = Matrix.CreateScale( 1 + amountOfScaling );
                    scaleDirection = 0;
                }
                // If our amount of scaling is almost the far zoom bound, set it to the far zoom bound and set the proper scaling.
                // Also set the direction to 0, because we don't need to zoom anymore as we're now in the zoom bounds again.
                else if (Math.Abs(farZoomBound - amountOfScaling) < KEYBOARD_ZOOM_SPEED)
                {
                    amountOfScaling = farZoomBound;
                    scaling = Matrix.CreateScale( 1 + amountOfScaling );
                    scaleDirection = 0;
                }
                // If there is a direction to zoom in, zoom to that direction each step
                else
                {
                    amountOfScaling += KEYBOARD_ZOOM_SPEED * scaleDirection;
                    scaling = Matrix.CreateScale( 1 + amountOfScaling );
                }
            }

            // If the direction of scaling is 0, we are in the zoom bounds and don't need to zoom anymore. The animation can be stopped
            if (scaleDirection == 0)
            {
                isToNewZoomBoundsAnimating = false;
            }
        }

        private void StepToCentreScreenAnimation()
        {
            // Determine the direction to move in, depending on the offset we already have, to center the board on the
            // screen again
            Vector2 translateDirection = new Vector2(amountOfTranslation.X == centreScreen.X ? 0 : amountOfTranslation.X < centreScreen.X ? 1 : -1,
                                                      amountOfTranslation.Y == centreScreen.Y ? 0 : amountOfTranslation.Y < centreScreen.Y ? 1 : -1);
            if (translateDirection != Vector2.Zero)
            {
                // If we can move, determine the actual translation. This is a correction on the movement being smaller than
                // the step size. If it is smaller, just move that bit and not a whole step
                Vector2 actualTranslation =
                        new Vector2(
                                Math.Abs(centreScreen.X - amountOfTranslation.X) < KEYBOARD_MOVEMENT_SPEED
                                        ? Math.Abs(amountOfTranslation.X - centreScreen.X) * translateDirection.X
                                        : KEYBOARD_MOVEMENT_SPEED * translateDirection.X,
                                Math.Abs(centreScreen.Y - amountOfTranslation.Y) < KEYBOARD_MOVEMENT_SPEED
                                        ? Math.Abs(amountOfTranslation.Y - centreScreen.Y) * translateDirection.Y
                                        : KEYBOARD_MOVEMENT_SPEED * translateDirection.Y);
                Move(actualTranslation.X, actualTranslation.Y);
            }

            if (amountOfTranslation == centreScreen)
            {
                isToCentreScreenAnimating = false;
            }
        }
        #endregion

        #region Doing Moves
        private void DoMove(int tileIdFrom, int tileIdTo, int tileToIdMove)
        {
            if (!humanMadeMove)
            {
                ResetSelectedTiles();
                
                try
                {
                    if (tileIdTo != -1 && tileList[tileIdTo].Ghost && tileToIdMove == -1) return;

                    Engine.TryMove((short)tileIdFrom, (short)tileIdTo, (short)tileToIdMove);

                    MoveCone(tileIdFrom, tileIdTo, tileToIdMove);

                    if (tileToIdMove != -1)
                        MoveTile(tileToIdMove, tileIdTo);

                    exceptionHandler.Exception = null;

                    humanMadeMove = true;
                }
                catch (InvalidMoveException e)
                {
                    exceptionHandler.Exception = e;
                }
            }
        }

        private void DoAutoMove()
        {
            if (!isTerminal)
            {
                waitForTurnAnimation.IsActive = true;

                short tileIdFromShort = -1;
                short tileIdToShort = -1;
                short tileToIdMoveShort = -1;

                Engine.aiDepth = (short)SettingsManager.Instance.PlyDepth;
                Engine.DoAutoMove(ref tileIdFromShort, ref tileIdToShort, ref tileToIdMoveShort);

                aiTileFrom = tileIdFromShort;
                aiTileTo = tileIdToShort;
                aiTileToMove = tileToIdMoveShort;

                waitForTurnAnimation.IsActive = false;
                aiMadeMove = true;
            }
        }

        private void UndoMove()
        {
            short tileIdFromShort = -1;
            short tileIdToShort = -1;
            short tileToIdMoveShort = -1;

            Engine.UndoMove(ref tileIdFromShort, ref tileIdToShort, ref tileToIdMoveShort);

            if (tileIdFromShort == -1)
            {
                coneList.Remove(tileIdToShort);
            }
            else
            {
                MoveCone(tileIdToShort, tileIdFromShort, -1);

                if (tileToIdMoveShort != -1)
                {
                    MoveTile(tileIdToShort, tileToIdMoveShort);
                }
            }
        }

        private void MoveCone(int tileIdFrom, int tileIdTo, int tileToIdMove)
        {
            if (tileIdFrom == -1)
            {
                coneList.Add(tileIdTo, Engine.currentBoard.CurrentPlayer == Player.Red ? ConeType.Red : ConeType.White);
            }
            else
            {
                // Change TileIndex from Cone
                for (int i = 0; i < coneList.Cones.Count; i++)
                {
                    // Check which cone was selected, on that one do the move
                    if (coneList.Cones[i].TileId == tileIdFrom)
                    {
                        // Check if should be flipped..
                        bool shouldFlip = Engine.currentBoard.HasToBeFlipped;

                        coneList.Move(coneList.GetIdByIndex(i), tileIdTo, shouldFlip);

                        break;
                    }
                }
            }
        }

        private void MoveTile(int tileIdMoveFrom, int tileIdMoveTo)
        {
            // animate a tile moving from - to
            tileList.Move( tileIdMoveFrom, tileIdMoveTo );
        }
        #endregion
        #endregion

        #region Public Methods
        public void NewGame(GameType gameType)
        {
            currentGameType = gameType;

            Engine.NewGame();

            hud.Reset();
            hud.Start();

            basicEffect.World = Matrix.Identity;
            rotationX = Matrix.Identity;
            degreesOfRotationX = 0;
            rotationZ = Matrix.Identity;
            degreesOfRotationZ = 0;
            scaling = Matrix.Identity;
            amountOfScaling = 0;
            translation = Matrix.Identity;
            amountOfTranslation = Vector3.Zero;
            RotateX(-45);

            DebugController.IsDebugMode = false;

            hasAddACone = false;
            moveTile = false;
            isTerminal = false;
            terminalState.ResetWinningPlayer();

            ResetSelectedTiles();

            tileList.NewGame();
        }

        public void SetResolution()
        {
            graphicsDeviceManager.PreferredBackBufferHeight = SettingsManager.Instance.ResolutionHeight;
            graphicsDeviceManager.PreferredBackBufferWidth = SettingsManager.Instance.ResolutionWidth;

            graphicsDeviceManager.IsFullScreen = SettingsManager.Instance.Fullscreen;

            basicEffect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45),
                                                                         (float)SettingsManager.Instance.ResolutionWidth /
                                                                         (float)SettingsManager.Instance.ResolutionHeight,
                                                                         0.1f, 100f);

            graphicsDeviceManager.ApplyChanges();
        }
        #endregion

        #region UpdateDraw Cycle
        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            debugController.Update(gameTime);
            designations.Update(gameTime);

            if (IsInMenu)
            {
                if (!isGamePaused && !isInitial)
                {
                    isGamePaused = true;
                    hud.Pause();
                }
                menu.Update(gameTime);
            }
            else if (!waitForTurnAnimation.IsActive)
            {
                isInitial = false;
                if (isGamePaused)
                {
                    isGamePaused = false;
                    hud.Pause();
                }

                #region Key Events
                KeyboardState keyboardState = Keyboard.GetState();

                if (keyboardState.IsKeyUp(Keys.Escape) && isKeyEscapeDown)
                {
                    IsInMenu = true;
                }

                if (!isAnimating)
                {
                    if (keyboardState.IsKeyUp(Keys.D) && isKeyDDown)
                    {
                        DebugController.IsDebugMode = !DebugController.IsDebugMode;
                    }
                    if (keyboardState.IsKeyDown(Keys.Left))
                    {
                        RotateZ(KEYBOARD_ROTATION_SPEED);
                    }
                    if (keyboardState.IsKeyDown(Keys.Right))
                    {
                        RotateZ(-KEYBOARD_ROTATION_SPEED);
                    }
                    if (keyboardState.IsKeyDown(Keys.Up))
                    {
                        RotateX(-KEYBOARD_ROTATION_SPEED);
                    }
                    if (keyboardState.IsKeyDown(Keys.Down))
                    {
                        RotateX(KEYBOARD_ROTATION_SPEED);
                    }
                    if (keyboardState.IsKeyDown(Keys.PageDown))
                    {
                        Zoom(KEYBOARD_ZOOM_SPEED);
                    }
                    if (keyboardState.IsKeyDown(Keys.PageUp))
                    {
                        Zoom(-KEYBOARD_ZOOM_SPEED);
                    }
                    if (keyboardState.IsKeyDown(Keys.NumPad4))
                    {
                        Move(-KEYBOARD_MOVEMENT_SPEED, 0);
                    }
                    if (keyboardState.IsKeyDown(Keys.NumPad6))
                    {
                        Move(KEYBOARD_MOVEMENT_SPEED, 0);
                    }
                    if (keyboardState.IsKeyDown(Keys.NumPad8))
                    {
                        Move(0, KEYBOARD_MOVEMENT_SPEED);
                    }
                    if (keyboardState.IsKeyDown(Keys.NumPad2))
                    {
                        Move(0, -KEYBOARD_MOVEMENT_SPEED);
                    }
                    if (keyboardState.IsKeyUp(Keys.R) && isKeyRDown)
                    {
                        isFirstCall = true;
                        isToPlayerAnimating = true;
                    }
                    if (keyboardState.IsKeyUp(Keys.T) && isKeyTDown)
                    {
                        isToTopAnimating = true;
                    }
                }

                isKeyEscapeDown = keyboardState.IsKeyDown(Keys.Escape);
                isKeyDDown = keyboardState.IsKeyDown(Keys.D);
                isKeyRDown = keyboardState.IsKeyDown(Keys.R);
                isKeyTDown = keyboardState.IsKeyDown(Keys.T);
                #endregion

                bool shouldUndo = false;

                #region Mouse Events
                MouseState mouseState = Mouse.GetState();

                Ray tempMouseRay = GetRealMouseCoordinates();

                bool hasMouseMoved = false;

                if (mouseMoveRay != tempMouseRay)
                {
                    mouseMoveRay = tempMouseRay;
                    hasMouseMoved = true;
                }

                if (hasMouseMoved && !isAnimating)
                {
                    SelectObjectAt(mouseMoveRay, MouseEvent.Hover);
                }

                if (mouseState.LeftButton != ButtonState.Pressed && this.isLeftMouseDown)
                    hasAddACone = false;

                if (!isAnimating)
                {
                    // Left mouse button enables object selection
                    if (mouseState.LeftButton == ButtonState.Released && this.isLeftMouseDown && !isAnimating)
                    {
                        // Select the object we are pointing at with the mouse
                        SelectObjectAt(mouseMoveRay, MouseEvent.LeftClick);
                    }

                    #region Moving the Board
                    // Left mouse button dragging enables view dragging (moving the board) when not clicking on the board
                    if (this.isLeftMouseDown && tileList.Hovered == -1 && coneList.Hovered == -1)
                    {
                        // Get the difference between the last click and the current click positions
                        Ray mousePositionDifference = new Ray(mouseLeftDownRay.Position - mouseMoveRay.Position,
                                                               mouseLeftDownRay.Direction - mouseMoveRay.Direction);

                        bool hasMovedHorizontal = mousePositionDifference.Position.X != 0;
                        bool hasMovedVertical = mousePositionDifference.Position.Y != 0;
                        float amountX = 0;
                        float amountY = 0;

                        // If the mouse was moved sideways
                        if (hasMovedHorizontal)
                        {
                            amountX = mousePositionDifference.Position.X * MOUSE_MOVEMENT_MULTIPLIER;
                        }
                        // If the mouse was moved up or down
                        if (hasMovedVertical)
                        {
                            amountY = mousePositionDifference.Position.Y * MOUSE_MOVEMENT_MULTIPLIER;
                        }

                        if (hasMovedHorizontal || hasMovedVertical)
                        {
                            if (SettingsManager.Instance.IsMouseInverted)
                                Move(amountX, amountY);
                            else
                                Move(-amountX, -amountY);
                        }

                        // Set the mouse position for the next update. This way we will drag according to the mouse movement
                        // We also recalculate the mouse coordinates, because the drag moving changes the camera view. Without
                        // recalculating, we will always be dragging, unless we release the mouse button
                        mouseLeftDownRay = GetRealMouseCoordinates();
                    }
                    #endregion

                    // Ensures the left mouse button drag doesn't reset with each click
                    mouseLeftDownRay = mouseMoveRay;

                    if (!isTerminal && !moveTile)
                    {
                        // Placing a cone
                        int nrOfCones = 12;
                        if (!hasAddACone && coneList.Cones.Count() < nrOfCones && tileList.Selected != -1)
                        {
                            DoMove(-1, tileList.Selected, -1);
                            hasAddACone = true;
                            tileList.Deselect();
                        }
                    }

                    // If we press the middle mouse button, the same action as pressing "R" should initiate
                    if (mouseState.MiddleButton == ButtonState.Released && isMiddleMouseDown)
                    {
                        isFirstCall = true;
                        isToPlayerAnimating = true;
                    }

                    // if tileFrom & tileTo are determined, [and there is a tile at this position but not a cone..]
                    // then move tile, flip cone if necessary
                    if (!isTerminal && tileFrom != -1 && tileTo != -1 && tileToMove != -1 && tileList[tileTo].Ghost)
                    {
                        DoMove(tileFrom, tileTo, tileToMove);
                    } 
                    else if (!isTerminal && tileFrom != -1 && tileTo != -1)// && tileToMove == -1)
                    {
                        if (!tileList[tileTo].Ghost)
                            DoMove(tileFrom, tileTo, -1);
                    }

                    // Right mouse button click enables deselecting a game element
                    if (mouseState.RightButton == ButtonState.Released && isRightMouseDown && !isTerminal)
                    {
                        ResetSelectedTiles();
                        shouldUndo = true;
                    }
                    // Right mouse button drag enables view dragging
                    else if (isRightMouseDown)
                    {
                        // Get the difference between the last click and the current click positions
                        Ray mousePositionDifference = new Ray(mouseRightDownRay.Position - mouseMoveRay.Position,
                                                               mouseRightDownRay.Direction - mouseMoveRay.Direction);

                        // If the mouse was moved sideways
                        if (mousePositionDifference.Position.X != 0)
                        {
                            // Rotate the world over the Z axis (sideways)
                            if (SettingsManager.Instance.IsMouseInverted)
                                RotateZ((mousePositionDifference.Position.X * MOUSE_ROTATION_MULTIPLIER));
                            else
                                RotateZ(-(mousePositionDifference.Position.X * MOUSE_ROTATION_MULTIPLIER));
                        }
                        // If the mouse was moved up or down
                        if (mousePositionDifference.Position.Y != 0)
                        {
                            // Rotate the world over the X axis (up or down)
                            if (SettingsManager.Instance.IsMouseInverted)
                                RotateX(-mousePositionDifference.Position.Y * MOUSE_ROTATION_MULTIPLIER);
                            else
                                RotateX(mousePositionDifference.Position.Y * MOUSE_ROTATION_MULTIPLIER);
                        }

                        // Set the mouse position for the next update. This way we will rotate according to the mouse movement
                        // We also recalculate the mouse coordinates, because the drag rotation changes the camera view. Without
                        // recalculating, we will always be dragging, unless we release the mouse button
                        mouseRightDownRay = GetRealMouseCoordinates();
                    }
                    // Ensures the right mouse button drag doesn't reset with each click
                    else if (mouseState.RightButton == ButtonState.Pressed)
                    {
                        mouseRightDownRay = mouseMoveRay;
                    }

                    if (scrollValue != mouseState.ScrollWheelValue)
                    {
                        int difference = mouseState.ScrollWheelValue - scrollValue;

                        Zoom(difference * MOUSE_ZOOM_MULTIPLIER);
                    }
                }

                // Get the states of the mouse buttons for the next update
                this.isLeftMouseDown = mouseState.LeftButton == ButtonState.Pressed;
                this.isMiddleMouseDown = mouseState.MiddleButton == ButtonState.Pressed;
                this.isRightMouseDown = mouseState.RightButton == ButtonState.Pressed;
                this.scrollValue = mouseState.ScrollWheelValue;
                #endregion

                #region Animations
                if (isToTopAnimating)
                {
                    StepToTopAnimation();
                }

                if (isToPlayerAnimating)
                {
                    StepToPlayerAnimation();
                }

                if (isToNewZoomBoundsAnimating)
                {
                    StepToNewZoomBoundsAnimation();
                }

                if (isToCentreScreenAnimating)
                {
                    StepToCentreScreenAnimation();
                }

                isAnimating = isToTopAnimating || isToPlayerAnimating || isMovingConeAnimating || isFlippingConeAnimating || isToNewZoomBoundsAnimating || isToCentreScreenAnimating;
                #endregion

                #region Terminal State
                if ( aiMadeMove || humanMadeMove )
                {
                    if ( !IsBusy )
                    {
                        if( Engine.currentBoard.IsTerminal() )
                        {
                            ResetSelectedTiles();
                            isTerminal = true;
                        }
                    }
                }

                if ( !IsBusy && isTerminal )
                {
                    terminalState.SetWinningPlayer( Engine.currentBoard.CurrentPlayer.ToString() );

                    for ( int i = 0; i < coneList.Cones.Count; i++ )
                    {
                        if ( coneList.Cones[ i ].ConeType == ConeType.Red && Engine.currentBoard.CurrentPlayer == Player.Red ||
                            coneList.Cones[ i ].ConeType == ConeType.White && Engine.currentBoard.CurrentPlayer == Player.White )
                            coneList.Cones[ i ].Dance();
                    }
                }

                #endregion

                #region Doing Moves
                if (humanMadeMove || aiMadeMove)
                {
                    // If a human made a move, wait 1 second to allow the human to undo the move, then initiate an ai move
                    if ( humanMadeMove )
                    {
                        if ( !isAnimating )
                        {
                            // Every update we add the elapsed time, this makes the undoTimer a timer
                            undoTimer += gameTime.ElapsedGameTime;

                            // If we waited 1 second, initiate the ai move
                            if ( undoTimer.Seconds > 1 )
                            {
                                ResetSelectedTiles();

                                humanMadeMove = false;
                                undoTimer = TimeSpan.Zero;

                                // Start a thread for the ai move so we don't have to wait for it and our front-end stays responsive
                                Thread thread = new Thread( DoAutoMove );
                                thread.Start();
                            }
                            else
                            {
                                if ( shouldUndo )
                                {
                                    ResetSelectedTiles();

                                    humanMadeMove = false;
                                    undoTimer = TimeSpan.Zero;

                                    UndoMove();
                                }
                            }
                        }
                    }

                    // Make the move for the AI (To avoid nasty threading issues we execute the MoveCone only when not
                    // in a different thread).
                    if ( aiMadeMove )
                    {
                        if ( aiTileToMove != -1 )
                        {
                            MoveTile( aiTileToMove, aiTileTo );
                        }

                        // Do the actual move in the front-end (the engine has already done it)
                        MoveCone( aiTileFrom, aiTileTo, aiTileToMove );
                        ResetSelectedTiles();
                        aiMadeMove = false;
                    }

                    if (Engine.currentBoard.IsTerminal())
                    {
                        isTerminal = true;
                    }
                }

                #endregion

                #region Debug Information
                DebugController.AddDebugViewInfo(String.Format("Camera Position: ({0}, {1}, {2})",
                                                                 cameraPosition.X,
                                                                 cameraPosition.Y,
                                                                 cameraPosition.Z));
                DebugController.AddDebugViewInfo(String.Format("Camera Target: ({0}, {1}, {2})",
                                                                 cameraTarget.X,
                                                                 cameraTarget.Y,
                                                                 cameraTarget.Z));
                DebugController.AddDebugViewInfo(String.Format("Rotation X: {0}", degreesOfRotationX));
                DebugController.AddDebugViewInfo(String.Format("Rotation Z: {0}", degreesOfRotationZ));
                DebugController.AddDebugViewInfo(String.Format("Zoom: {0}", amountOfScaling));
                DebugController.AddDebugViewInfo(String.Format("Translation: ({0}, {1}, {2})",
                                                                 amountOfTranslation.X,
                                                                 amountOfTranslation.Y,
                                                                 amountOfTranslation.Z));

                DebugController.AddDebugAnimationOrErrorInfo(String.Format("Is Animating: {0}", isAnimating));

                if (isToTopAnimating)
                {
                    DebugController.AddDebugAnimationOrErrorInfo(String.Format("To Top Animation is running"));
                }
                if (isToPlayerAnimating)
                {
                    DebugController.AddDebugAnimationOrErrorInfo(String.Format("To Player Animation is running"));
                }
                if (isToNewZoomBoundsAnimating)
                {
                    DebugController.AddDebugAnimationOrErrorInfo(String.Format("To New Zoom Bounds Animation is running"));
                }

                DebugController.AddDebugGameInfo("coneList.Cones.Count() " + coneList.Cones.Count());
                DebugController.AddDebugGameInfo("hasAddACone " + hasAddACone);

                string coneSelect = (coneList.Selected != -1) ? coneList[coneList.Selected].TileId.ToString() : "-1";
                DebugController.AddDebugGameInfo("coneList.Selected " + coneSelect);

                DebugController.AddDebugGameInfo("tileFrom: " + tileFrom);
                DebugController.AddDebugGameInfo("tileTo: " + tileTo);
                DebugController.AddDebugGameInfo("tileToMove: " + tileToMove);

                DebugController.AddDebugGameInfo("Is terminal: " + isTerminal);
                if (isTerminal)
                    DebugController.AddDebugGameInfo("Winning Player: " + Engine.currentBoard.CurrentPlayer);
                #endregion
            }
            terminalState.Update( gameTime );

            #region HUD
            hud.Update(gameTime);
            #endregion

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            GraphicsDevice.RenderState.MultiSampleAntiAlias = true;

            if (IsInMenu)
            {
                menu.Draw(gameTime);
            }
            else
            {
                spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.SaveState);
                Rectangle src = new Rectangle(0, 0, backgroundTexture.Width, backgroundTexture.Height);
                Rectangle dest = new Rectangle(0, 0, SettingsManager.Instance.ResolutionWidth, SettingsManager.Instance.ResolutionHeight);
                spriteBatch.Draw(backgroundTexture, dest, src, Color.White);
                spriteBatch.End();
                
                base.Draw(gameTime);

                
                hud.Draw(gameTime);
                designations.Draw(gameTime);
                debugController.Draw( gameTime );
                terminalState.Draw( gameTime );
                
            }
        }
        #endregion
    }
}